package com.su.bo.process.util

import org.json.*
import java.io.StringReader

@Suppress("unused")
object XmlToJson : XML() {
    /**
     * Xml转换JSON
     * 标签的属性会被转换为@开头的Key，数组需要指定层级用'.'作为分割符
     * @param xml 源数据XML
     * @param arrayPath 需要强制指定为数组的KEY
     * @return XML字符
     */
    @JvmStatic
    @Throws(JSONException::class)
    fun toJsonByXml(xml: String?, arrayPath: Set<String?>?): String {
        val config = XMLParserConfiguration.KEEP_STRINGS.withConvertNilAttributeToNull(true).withForceList(arrayPath)
        val jsonObject = JSONObject()
        val x = XMLTokener(xml?.let { StringReader(it) })
        while (x.more()) {
            x.skipPast("<")
            if (x.more()) {
                parse(x, jsonObject, null, config, null)
            }
        }
        return jsonObject.toString()
    }

    @Throws(JSONException::class)
    private fun parse(
        xmlToken: XMLTokener,
        context: JSONObject,
        name: String?,
        config: XMLParserConfiguration,
        tokenPath: String?
    ): Boolean {
        val c: Char
        var i: Int
        val jsonObject: JSONObject
        var string: String
        val tagName: String?
        var token: Any?
        val tempTokenPath: String
        var xmlXsiTypeConverter: XMLXsiTypeConverter<*>?
        token = xmlToken.nextToken()
        if (BANG.equals(token)) {
            c = xmlToken.next()
            if (c == '-') {
                if (xmlToken.next() == '-') {
                    xmlToken.skipPast("-->")
                    return false
                }
                xmlToken.back()
            } else if (c == '[') {
                token = xmlToken.nextToken()
                if ("CDATA" == token) {
                    if (xmlToken.next() == '[') {
                        string = xmlToken.nextCDATA()
                        if (string.isNotEmpty()) {
                            context.accumulate(config.getcDataTagName(), string)
                        }
                        return false
                    }
                }
                throw xmlToken.syntaxError("Expected 'CDATA['")
            }
            i = 1
            do {
                token = xmlToken.nextMeta()
                if (token == null) {
                    throw xmlToken.syntaxError("Missing '>' after '<!'.")
                } else if (LT.equals(token)) {
                    i += 1
                } else if (GT.equals(token)) {
                    i -= 1
                }
            } while (i > 0)
            return false
        } else if (QUEST.equals(token)) {

            // <?
            xmlToken.skipPast("?>")
            return false
        } else if (SLASH.equals(token)) {

            // Close tag </
            token = xmlToken.nextToken()
            if (name == null) {
                throw xmlToken.syntaxError("Mismatched close tag $token")
            }
            if (token != name) {
                throw xmlToken.syntaxError("Mismatched $name and $token")
            }
            if (!GT.equals(xmlToken.nextToken())) {
                throw xmlToken.syntaxError("Misshaped close tag")
            }
            return true
        } else if (token is Char) {
            throw xmlToken.syntaxError("Misshaped tag")

            // Open tag <
        } else {
            tagName = token as String?
            tempTokenPath = if (null === tokenPath) tagName.toString() else "$tokenPath.$tagName"
            token = null
            jsonObject = JSONObject()
            var nilAttributeFound = false
            xmlXsiTypeConverter = null
            while (true) {
                if (token == null) {
                    token = xmlToken.nextToken()
                }
                //添加标签属性前缀
                if (GT != token) {
                    token = "@$token"
                }
                // attribute = value
                if (token is String) {
                    string = token
                    token = xmlToken.nextToken()
                    if (EQ.equals(token)) {
                        token = xmlToken.nextToken()
                        if (token !is String) {
                            throw xmlToken.syntaxError("Missing value")
                        }
                        if (config.isConvertNilAttributeToNull && NULL_ATTR == string && java.lang.Boolean.parseBoolean(
                                token as String?
                            )
                        ) {
                            nilAttributeFound = true
                        } else if (config.xsiTypeMap != null && config.xsiTypeMap.isNotEmpty() && TYPE_ATTR == string) {
                            xmlXsiTypeConverter = config.xsiTypeMap[token]
                        } else if (!nilAttributeFound) {
                            jsonObject.accumulate(
                                string,
                                if (config.isKeepStrings) token else stringToValue(token as String?)
                            )
                        }
                        token = null
                    } else {
                        jsonObject.accumulate(string, "")
                    }
                } else if (SLASH.equals(token)) {
                    // Empty tag <.../>
                    if (GT.equals(xmlToken.nextToken())) {
                        throw xmlToken.syntaxError("Misshaped tag")
                    }
                    if (config.forceList.contains(tagName)) {
                        // Force the value to be an array
                        if (nilAttributeFound) {
                            context.append(tagName, JSONObject.NULL)
                        } else if (jsonObject.length() > 0) {
                            context.append(tagName, jsonObject)
                        } else {
                            context.put(tagName, JSONArray())
                        }
                    } else {
                        if (nilAttributeFound) {
                            context.accumulate(tagName, JSONObject.NULL)
                        } else if (jsonObject.length() > 0) {
                            context.accumulate(tagName, jsonObject)
                        } else {
                            context.accumulate(tagName, "")
                        }
                    }
                    return false
                } else if (GT.equals(token)) {
                    // Content, between <...> and </...>
                    while (true) {
                        token = xmlToken.nextContent()
                        if (token == null) {
                            if (tagName != null) {
                                throw xmlToken.syntaxError("Unclosed tag $tagName")
                            }
                            return false
                        } else if (token is String) {
                            string = token
                            if (string.isNotEmpty()) {
                                if (xmlXsiTypeConverter != null) {
                                    jsonObject.accumulate(
                                        config.getcDataTagName(),
                                        stringToValue(string, xmlXsiTypeConverter)
                                    )
                                } else {
                                    jsonObject.accumulate(
                                        config.getcDataTagName(),
                                        if (config.isKeepStrings) string else stringToValue(string)
                                    )
                                }
                            }
                        } else if (LT.equals(token)) {
                            // Nested element
                            if (parse(xmlToken, jsonObject, tagName, config, tempTokenPath)) {
                                if (config.forceList.contains(tempTokenPath)) {
                                    // Force the value to be an array
                                    if (jsonObject.length() == 0) {
                                        context.put(tagName, JSONArray())
                                    } else if (jsonObject.length() == 1 && jsonObject.opt(config.getcDataTagName()) != null) {
                                        context.append(tagName, jsonObject.opt(config.getcDataTagName()))
                                    } else {
                                        context.append(tagName, jsonObject)
                                    }
                                } else {
                                    if (jsonObject.length() == 0) {
                                        context.accumulate(tagName, "")
                                    } else if (jsonObject.length() == 1 && jsonObject.opt(config.getcDataTagName()) != null) {
                                        context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()))
                                    } else {
                                        context.accumulate(tagName, jsonObject)
                                    }
                                }
                                return false
                            }
                        }
                    }
                } else {
                    throw xmlToken.syntaxError("Misshaped tag")
                }
            }
        }
    }
}